<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Marvin JS Example - Structure checker</title>
<link type="text/css" rel="stylesheet" href="../../css/doc.css" />
<link type="text/css" rel="stylesheet" href="../../js/lib/rainbow/github.css" />

<script src="../../js/lib/rainbow/rainbow-custom.min.js"></script>
<script src="../../js/lib/jquery-1.9.1.min.js"></script>
<script src="../../js/webservices.js"></script>
<script src="../../gui/gui.nocache.js"></script>
<script src="../../gui/lib/promise-1.0.0.min.js"></script>
<script src="../../js/marvinjslauncher.js"></script>
<script src="molchange.js"></script>
<script>

var marvinSketcherInstance;

function onSketchLoaded(sketcherInstance) {
	marvinSketcherInstance = sketcherInstance;
	// aggregator for molecule change events
	new MolChangeHandler(sketcherInstance, onMolChange);
}

var last = null;

function onMolChange(e) {
	last = e;
	reset();
	e.target.exportStructure("mrv").then(function(source) {
		if(!e.isDeprecated) { // unless a newer molchange event deprecate this event
			e.source = source;
			Checkers.check(source);
		}
	},alert);
}

function reset() {
	// reset current highlight
	marvinSketcherInstance.setHighlight({});
	$('#checkers-panel-result').empty();
}

function onConfigChange(e) {
	// reevaluate last consumed molchange event when congfiguration is changed
	if(last && !last.isDeprecated && (typeof last.source == 'string')) {
		reset();
		Checkers.check(last.source);
	}
}

var CheckersWS = (function(){
	
	function getServices(data) {
		var result = [];
		if($.isArray(data)) {
			for(var i = 0; i < data.length; i++) {
				if(data[i]['checkerIds']){
					var ids = data[i]['checkerIds'];
					if($.isArray(ids)) {
						for(var j = 0; j < ids.length; j++)
						result.push(ids[j]);
					}
				}
			}
		}
		return result;
	}
	
	var wsBase = getDefaultServicesPrefix();
	
	return new Promise(function(resolve, reject) {
		
		$.ajax({
			'url': wsBase + '/rest-v0/util/checkers',
			'type': 'GET',
			'dataType': 'json'
		}).done(function (data, textStatus, jqXHR) {
			var services = getServices(data);
			resolve(services);
		}).fail(function onFail(qXHR, textStatus, errorThrown) {
			// error handling if request failed
			if (qXHR.status == 0) { // UNSENT request
				reject(new Error("Structure Checker failed.\nThe request has not been sent.\nThe request may violate the cross domain policy."));
			} else {
				reject(new Error("Structure Checker failed (status code "+ qXHR.status + ")\n" + qXHR.responseText));
			}
		});
	});

}());

$(document).ready(function handleDocumentReady (e) {
	var config = [ 
		{id: "bondlength", color: "#ffe211", title: "Bond Length"},
		{id: "bondangle", color: "#ef6671", title: "Bond Angle"},
		{id: "atommap", color: "#d26ff1", title: "Atom Map"}
	];
	CheckersWS.then(function(services) {
		for(var i = 0; i < config.length; i++) {
			if($.inArray(config[i].id, services)) {
				Checkers.add(config[i].id, config[i].color, config[i].title);
			}
		}
		MarvinJSUtil.getEditor("#sketch").then(onSketchLoaded, function () {
			alert("Cannot retrieve sketcher instance from iframe");
		});
	}, function(error) {
		$('#checkers-panel-result').html('<span style="color:red">structure checker is not available</span>');
	});
});


var Checkers = (function() {

	var widgets = [];
	
	/** Appends a new checker to the document.
	* @param id - the name of the checker as referred in JChem WS (see JChem WS documentation)
	* @param color - colorize checker result (atoms/bonds) in the editor with this color
	* @param title - the short description of the checker that displays in the config panel below.
	*/
	function add(id, color, title) {
		var widget = new CheckerWidget(id, color, title);
		$("#checkers-panel-config").append(widget.asWidget());
		widgets.push(widget);
	}
	
	/**
	* Performs the structure checking on the given molecule source.
	* @param source - the molecule source in MRV format.
	*/
	function check(source) {
		var json = {};
		json.structure = source;
		json.parameters = {};
		json.parameters.checkers = [];
		for(var i = 0; i < widgets.length; i++) {
			var widget = widgets[i];
			if(widget.isEnabled()) {
				json.parameters.checkers.push({"checkerId": widget.getId() });
			}
		}
		if(json.parameters.checkers.length == 0) {
			return;
		}
		send(json);
	}
	
	/* Sends an asyn request to Structure checker WS. */
	function send(json) {
		var wsBase = getDefaultServicesPrefix();
		// arrange the input for the Structure Checker web service
		$.ajax({
			"url": wsBase + "/rest-v0/util/calculate/structureChecker"
			,"type": "POST"
			,"dataType": "json"
			,"contentType": "application/json"
			,"data": JSON.stringify(json)
		}).done(function (data, textStatus, jqXHR) {
			onSuccess(data);
		}).fail(onFail);
	}
	
	/* Retrieve the checker widget with the given id */
	function getWidget(checkerId) {
		for(var i = 0; i < widgets.length; i++) {
			if(widgets[i].getId() == checkerId) {
				return widgets[i];
			}
		}
		return null;
	}
	
	/* Checks whether it can be cast as checker result. */
	function isCheckerResult(data) {
		return (typeof data.errors == 'object' &&
		typeof data.checkerName == 'string' &&
		typeof data.description == 'string');
	}
	
	/* Retrieves index of atoms. */
	function getAtoms(atoms) {
		var result = []
		if(typeof atoms == 'object') {
			for(var i = 0; i < atoms.length; i++) {
				if(typeof atoms[i].atomIndex == 'number') {
					result.push(atoms[i].atomIndex+1);
				}
			}
		}
		return result;
	}

	/* Retrieves index of bonds. */
	function getBonds(bonds) {
		var result = [];
		if(typeof bonds == 'object') {
			for(var i = 0; i < bonds.length; i++) {
				if(typeof bonds[i].atom1Index == 'number' && typeof bonds[i].atom2Index == 'number') {
					result.push((bonds[i].atom1Index+1) +"-" + (bonds[i].atom2Index+1));
				}
			}
		}
		return result;
	}

	/* Callback to process web service result. */
	function onSuccess(data) {
		var highlights = [];
		if(typeof data.headers == 'object') {
			for(checkerId in data.headers) {
				if(data.headers.hasOwnProperty(checkerId) && isCheckerResult(data[checkerId])) {
					var widget = getWidget(checkerId);
					if(widget) {
						// display error message
						var message = "("+data[checkerId].checkerName+"): "+data[checkerId].description;
						$('#checkers-panel-result').append(widget.createErrorWidget(message));
						// calculate context to highlight
						if(typeof data[checkerId].errors == 'object') {
							var indexes = {};
							indexes.atoms = getAtoms(data[checkerId].errors.atoms);
							indexes.bonds = getBonds(data[checkerId].errors.bonds);
							if(indexes.atoms.length || indexes.bonds.length) { // add highlight unless context is empty
								highlights.push({
									'style': {
										'color': widget.getColor(),
										'opacity': 0.25
									},
									'indexes': indexes
								});
							}
						}
					}
				}
			}	
		}
		// highlight atoms and bonds
		marvinSketcherInstance.setHighlight(highlights);
	}
	
	/* callback for failed request */
	function onFail(qXHR, textStatus, errorThrown) {
		// error handling if request failed
		if (qXHR.status == 0) { // UNSENT request
			var errMsg = "ERROR: Structure Checker failed.\nThe request has not been sent.\nThe request may violate the cross domain policy.";
			alert(errMsg);
		} else {
			alert("ERROR: Structure Checker failed (status code "+ qXHR.status + ")\n" + qXHR.responseText);
		}
	}
	
	var CheckerWidget = (function() {
		
		function CheckerWidget(checkerId, color, title) {
			var listItem = $('<p>');
			var checkBox = $('<input>', { type: "checkbox", value: checkerId });
			checkBox[0].checked = true;
			checkBox.appendTo(listItem);
			var label = $('<span>');
			label.text(title);
			label.appendTo(listItem);
			var colorWidget = $('<input>', {id: "color-"+checkerId, type: "color"});
			colorWidget.val(color);
			colorWidget.appendTo(listItem);
			var link = $('<a>', {
				"href": "https://docs.chemaxon.com/display/docs/"+title.replace(" ", "+"),
				"target": "_blank"
			});
			link.text("description");
			link.appendTo(listItem);
			checkBox.bind('change', onConfigChange);
			colorWidget.bind('change', onConfigChange);
			return {
				asWidget: function() {
					return listItem;
				},
				getId: function() {
					return checkerId;
				},
				getColor: function getColor() {
					return colorWidget.val();
				},
				isEnabled: function() {
					return checkBox.is( ":checked" );
				},
				createErrorWidget: function(text) {
					var msg = $('<div>');
					$("<div>", {"class": "legend", "style": "background-color:"+this.getColor()}).appendTo(msg);
					var label = $("<div>", { "text": text });
					label.appendTo(msg);
					return msg;
				} 
			}
		}
		
		return CheckerWidget;
	}());
	
	return {
		add: add,
		check: check
	}
}());



</script>
<style>

#checkers-panel-config, #checkers-panel-result {
	display:table;
}

#checkers-panel-config>p {
	display:table-row;
}
#checkers-panel-config>p>* {
	display:table-cell;
	margin: 2px;
}

#checkers-panel-result>div {
	display: table-row;
}

#checkers-panel-result>div>div {
	display: table-cell;
	vertical-align: middle;
}

div.legend {
	float: left;
    width: 20px;
    height: 20px;
    margin: 5px;
    border-width: 1px;
    border-style: solid;
    border-color: white;
    opacity: 0.25;
}

</style>
</head>
<body>
<h1>Marvin JS Example - Structure Checker</h1>
<div style="clear: both; width: 100%; text-align: right;"><a href="../index.html">Back to index</a></div>
<p>Here is an example how to integrate Structure Checker web service into Marvin JS editor (In this case JChem WS requires the Structure Checker license).
If the molecule changes, its source will be sent to the Structure Checker to evaluate the structure with the selected types of checkers. 
If any structure error is detected, it is highlighted on the structure and a detailed error message appears below the editor.</p>
<p>In this example, only <strong>Bond Length</strong>, <strong>Bond Angle</strong> and <strong>Atom Map</strong> checkers are used.
The full list of checkers are available here: <a href="https://docs.chemaxon.com/display/docs/Checker+List">Checker List</a></p>
<div class="resizable">
	<iframe src="../../editorws.html" id="sketch" class="sketcher-frame"></iframe>
</div>
<div id="checkers-panel">
<h3>Structure Checker configuration</h3>
<div id="checkers-panel-config"></div>
<h3>Structure Errors</h3>
<div id="checkers-panel-result"></div>
</div>
<div>
<h2>Code comments</h2>
<p>This examples requires <code>molchange.js</code> that defines <code>MolChangeHandler</code> class to aggregate molecule change events.</p>
<p>First of all, <code>CheckersWS</code> checks which services are availables. It provides a Promise to notify when the list of the available
services arrived (or notify if services are unreachables).
<p>After that, the following steps will be executed:
<ul>
	<li>With the <code>add</code> function of the <code>Checkers</code> object, selected structure checkers are initialized.</li>
	<li>Retrieves the reference for the sketcher that runs in the iframe and runs the <code>onSketchLoaded</code> function.</li>
</ul>
<pre><code data-language="javascript">$(document).ready(function handleDocumentReady (e) {
	var config = [ 
		{id: "bondlength", color: "#ffe211", title: "Bond length"},
		{id: "bondangle", color: "#ef6671", title: "Bond angle"},
		{id: "atommap", color: "#d26ff1", title: "Atom map"}
	];
	CheckersWS.then(function(services) {
		for(var i = 0; i < config.length; i++) {
			if($.inArray(config[i].id, services)) {
				Checkers.add(config[i].id, config[i].color, config[i].title);
			}
		}
		MarvinJSUtil.getEditor("#sketch").then(onSketchLoaded, function () {
			alert("Cannot retrieve sketcher instance from iframe");
		});
	}, function(error) {
		$('#checkers-panel-result').html('<span style="color:red">structure checker is not available</span>');
	});
});
});</code></pre>
<p>The <code>onSketchLoaded</code> function instantiates a <code>MolChangeHandler</code>. Its first parameter is the reference of the editor to listen.
The second one is a callback function that is performed at each mol change event.</p>
<pre><code data-language="javascript">function onSketchLoaded(sketcherInstance) {
	marvinSketcherInstance = sketcherInstance;
	// aggregator for molecule change events
	new MolChangeHandler(sketcherInstance, onMolChange);
}</code></pre>
<p>If you take a look at the <code>add</code> function of <code>Checkers</code>, you can see that it creates a <code>CheckerWidget</code> object for each checker.</p>
<pre><code data-language="javascript">	/** Appends a new checker to the document.
	* @param id - the name of the checker as referred in JChem WS (see JChem WS documentation)
	* @param color - colorizes the result (atoms/bonds) in the editor with this color
	* @param title - the short description of the checker that displays in the config panel below.
	*/
	function add(id, color, title) {
		var widget = new CheckerWidget(id, color, title);
		$("#checkers-panel-config").append(widget.asWidget());
		widgets.push(widget);
	}</code></pre>
<p>Returning to the <code>onMolChange</code> function, you can see that it resets the current highlight on the sketcher, gets the source of the current structure and performs a
structure check on it.</p> 
<pre><code data-language="javascript">function reset() {
	// reset current highlight
	marvinSketcherInstance.setHighlight({});
	$('#checkers-panel-result').empty();
}

var last = null;

function onMolChange(e) {
	last = e;
	reset();
	e.target.exportStructure("mrv").then(function(source) {
		if(!e.isDeprecated) { // unless a newer molchange event deprecate this event
			e.source = source;
			Checkers.check(source);
		}
	},alert);
}</code></pre>
<p>If you take a closer look at the <code>Checkers.check</code> function, you can see those <code>CheckerWidget</code>s are enabled where the checkboxes were previously checked. It creates the input for the Structure Checker web service.
Finally, it calls the <code>send</code> method with the created object.</p>
<pre><code data-language="javascript">	/**
	* Performs the structure checking on the given molecule source.
	* @param source - the molecule source in MRV format.
	*/
	function check(source) {
		var json = {};
		json.structure = source;
		json.parameters = {};
		json.parameters.checkers = [];
		for(var i = 0; i < widgets.length; i++) {
			var widget = widgets[i];
			if(widget.isEnabled()) {
				json.parameters.checkers.push({"checkerId": widget.getId() });
			}
		}
		if(json.parameters.checkers.length == 0) {
			return;
		}
		send(json);
	}</code></pre>
<p>The <code>send</code> invokes the web service, then call <code>onSuccess</code> function when the response of web service is received or <code>onFail</code> if any error occured.</p>
<pre><code data-language="javascript">	/* Sends an async request to Structure checker web service. */
	function send(json) {
		var wsBase = getDefaultServicesPrefix();
		// arrange the input for the Structure Checker web service
		$.ajax({
			"url": wsBase + "/rest-v0/util/calculate/structureChecker"
			,"type": "POST"
			,"dataType": "json"
			,"contentType": "application/json"
			,"data": JSON.stringify(json)
		}).done(function (data, textStatus, jqXHR) {
			onSuccess(data);
		}).fail(onFail);
	}</code></pre>
<p>The <code>onSuccess</code> function gets the response of the web service as a parameter (<code>data</code>). Its <code>header</code> field contains the id of the performed checkers.
Each checker result can be referred with its own id. E.g. <code>data.bondlength</code> describes the result of <code>bondlength</code> checker.
A checker result can contain many fields but only the following one are relevant in this case:
<ul>
	<li>checkerName: the title of the checker</li>
	<li>description: the error message</li>
	<li>errors: a complex object that also describes which atoms and bonds are affected.</li>
</ul>
The <code>checkerName</code> and the <code>decription</code> is displayed on the current page in the <code>checkers-panel-result</code> div.
Meanwhile the affected atoms and bonds are highlighted in the sketcher.
<pre><code data-language="javascript">/* Callback to process web service result. */
	function onSuccess(data) {
		var highlights = [];
		if(typeof data.headers == 'object') {
			for(checkerId in data.headers) {
				if(data.headers.hasOwnProperty(checkerId) && isCheckerResult(data[checkerId])) {
					var widget = getWidget(checkerId);
					if(widget) {
						// display error message
						var message = "("+data[checkerId].checkerName+"): "+data[checkerId].description;
						$('#checkers-panel-result').append(widget.createErrorWidget(message));
						// calculate context to highlight
						if(typeof data[checkerId].errors == 'object') {
							var indexes = {};
							indexes.atoms = getAtoms(data[checkerId].errors.atoms);
							indexes.bonds = getBonds(data[checkerId].errors.bonds);
							if(indexes.atoms.length || indexes.bonds.length) { // add highlight unless context is empty
								highlights.push({
									'style': {
										'color': widget.getColor(),
										'opacity': 0.25
									},
									'indexes': indexes
								});
							}
						}
					}
				}
			}	
		}
		// highlight atoms and bonds
		marvinSketcherInstance.setHighlight(highlights);
	}</code></pre>
<p>Changing of the configuration (check/uncheck checkbox or modify checker color) also triggers the checking of the structure
(whose source was already stored in the <code>source</code> property of the last molchange event).
<pre><code data-language="javascript">function onConfigChange(e) {
	// reevaluate last consumed molchange event when congfiguration is changed
	if(last && !last.isDeprecated && (typeof last.source == 'string')) {
		reset();
		Checkers.check(last.source);
	}
}</code></pre></p>
</div>
<div style="clear: both; width: 100%; text-align: right;"><a href="../index.html">Back to index</a></div>
</body>
</html>
